using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Hjson;
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.RuntimeDetour;
using RSCore.Files;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.ModLoader.Config;
using Terraria.ModLoader.Core;

namespace RSCore;

public static class TextLoader
{
	/// <summary>
	/// 导出Mod运行时所有的字符串
	/// </summary>
	/// <param name="mod"> </param>
	/// <param name="origin"> </param>
	/// <param name="targets"> </param>
	/// <returns> Package </returns>
	public static RSDirectory Export(Mod mod, GameCulture origin, params GameCulture[] targets)
	{
		var package = new RSDirectory(mod.Name);
		var ldstr = ExportLdstr(mod.File);

		RSDirectory directory = ExportText(mod, origin);
		directory.AddDirectory(ldstr with { });
		package.AddDirectory(directory);

		foreach (var target in targets)
		{
			directory = ExportText(mod, target);
			directory.AddDirectory(ldstr with { });
			package.AddDirectory(directory);
		}

		package.AddFile(new BuildFile("Build")
		{
			Author = string.Empty,
			Language = origin.Name,
			Version = mod.Version,
			Description = string.Empty,
			ModVersion = mod.Version,
			ModName = mod.Name,
		});

		return package;
	}

	public static void Import(Mod mod, RSDirectory package)
	{
		if (package.Directories.All(d => d.Name != Language.ActiveCulture.Name))
		{
			return;
		}

		ImportText(mod, package);

		if (ModHelper.SafeMode)
		{
			return;
		}

		ImportLdstr(mod, package);
	}

	public static void ImportText(Mod mod, RSDirectory package)
	{
		if (!package.TryGetFile("Build", out var temp))
		{
			return;
		}
		var build = (BuildFile)temp;

		Dictionary<string, TextPair> translations = new();
		var dir = package.GetDirectory(Language.ActiveCulture.Name);
		foreach (var (key, value) in dir.EnumerateFile()
			.OfType<IKeyConvertable>()
			.SelectMany(s => s.Convert()))
		{
			ref var pair = ref CollectionsMarshal.GetValueRefOrAddDefault(
				translations, KeyConverter.Instance.Convert(key), out _);
			pair.Translation = value;
		}

		if (package.TryGetDirectory(build.Language, out dir))
		{
			foreach (var (keyData, value) in dir.EnumerateFile()
				.OfType<IKeyConvertable>()
				.SelectMany(s => s.Convert()))
			{
				string key = KeyConverter.Instance.Convert(keyData);
				ref var pair = ref CollectionsMarshal.GetValueRefOrNullRef(
					translations, key);
				if (!Unsafe.IsNullRef(ref pair))
				{
					if (pair.Translation == value)
					{
						translations.Remove(key);
					}
					else
					{
						pair.Origin = value;
					}
				}
			}
		}

		TranslationHook.Instance.AddTranslation(translations, mod.Name);
	}

	private static void ImportLdstr(Mod mod, RSDirectory package)
	{
		if (!package.TryGetFile("Build", out var temp))
		{
			return;
		}
		var build = (BuildFile)temp;

		Dictionary<string, TextPair[]> ldstrText = new();
		var dir = package.GetDirectory(Language.ActiveCulture.Name);
		foreach (var file in dir.EnumerateFile().OfType<LdstrFile>())
		{
			ldstrText[file.Name] = file.Text.Select(s => default(TextPair) with { Translation = s }).ToArray();
		}

		dir = package.GetDirectory(build.Language);

		foreach (var file in dir.EnumerateFile().OfType<LdstrFile>())
		{
			if (!ldstrText.TryGetValue(file.Name, out var array))
			{
				continue;
			}

			if (array.Length != file.Text.Count)
			{
				ModHelper.Warn("LengthNotMatch", file.Name, file.Text.Count, array.Length);
				ldstrText.Remove(file.Name);
				continue;
			}

			for (int i = 0; i < array.Length; i++)
			{
				array[i].Origin = file.Text[i];
			}

			if (file.Text.SequenceEqual(array.Select(s => s.Translation)))
			{
				ldstrText.Remove(file.Name);
			}
		}

		LdstrHook.Instance.AddTranslation(ldstrText, mod.Name);
	}

	/// <summary>
	/// 导出Mod程序集中的字符串
	/// </summary>
	/// <param name="mod"> </param>
	/// <returns> Content </returns>
	public static RSDirectory ExportLdstr(TmodFile file)
	{
		var content = new RSDirectory("Ldstr");

		var asm = AssemblyDefinition.ReadAssembly(new MemoryStream(file.GetModAssembly()));
		foreach (var method in asm.MainModule.Types.SelectMany(t => t.Methods).Where(m => m.HasBody))
		{
			LdstrFile? ldstrFile = null;
			foreach (var ins in method.Body.Instructions.Where(ins => ins.OpCode == OpCodes.Ldstr))
			{
				var text = ins.Operand as string ?? string.Empty;
				if (string.IsNullOrWhiteSpace(text) || text.All(TextIO.Symbols.Contains))
				{
					continue;
				}
				else
				{
					ldstrFile ??= new LdstrFile(Wrapper.Create(method));
					ldstrFile.Text.Add(text);
				}
			}
			if (ldstrFile is not null)
			{
				content.AddFile(ldstrFile);
			}
		}
		return content;
	}

	/// <summary>
	/// 导出运行时的Mod文本
	/// </summary>
	/// <param name="mod"> </param>
	/// <param name="culture"> </param>
	/// <returns> Content </returns>
	public static RSDirectory ExportText(Mod mod, GameCulture culture)
	{
		var prefix = $"Mods.{mod.Name}.";
		var origCulture = Language.ActiveCulture;
		var del = LanguageManager.Instance.ProcessCopyCommandsInTexts;
		using (new Hook(del.Method, (Action<LanguageManager> orig, LanguageManager self) => { }))
		{
			LanguageManager.Instance.ActiveCulture = culture;
			LanguageManager.Instance.ResetBoundTexts();
			LanguageManager.Instance.ReloadLanguage();
		}
		IEnumerable<KeyValuePair<string, string>> FilterTranslations()
		{
			foreach (var (key, localizedText) in LanguageManager.Instance._localizedTexts)
			{
				string text = localizedText.Value;
				if (!key.StartsWith(prefix, StringComparison.Ordinal))
				{
					continue;
				}
				yield return new(key, text);
			}
		}

		var content = TextIO.ToRSDirectory(culture.Name, mod.Name, FilterTranslations());

		if (ConfigManager.Configs.TryGetValue(mod, out var configs))
		{
			foreach (var cfg in configs)
			{
				if (content.Directories.FirstOrDefault(d => d.Name == cfg.Name) is not RSDirectory dir)
				{
					continue;
				}
				content.RemoveDirectory(dir);
				var file = new ConfigFile(cfg.Name);
				Stack<RSDirectory> dirStack = new();
				Stack<string> keys = new();
				dirStack.Push(dir);
				while (dirStack.Count != 0)
				{
					var head = dirStack.Pop();
					foreach (var f in head.Files.OfType<TextFile>())
					{
						keys.Push(f.Name);
						file.ExtendedProperties.Add(string.Join('.', keys), f.Text);
						keys.Pop();
					}

					foreach (var d in head.Directories)
					{
						dirStack.Push(d);
					}
				}
			}
		}

		LanguageManager.Instance.ActiveCulture = origCulture;
		LanguageManager.Instance.ResetBoundTexts();
		LanguageManager.Instance.ReloadLanguage();
		return content;
	}

	/// <summary>
	/// 导出Mod文件中的Hjson文件
	/// </summary>
	/// <param name="mod"> </param>
	/// <returns> Package </returns>
	public static RSDirectory ExportHjson(string modName, out TmodFile file)
	{
		file = ModOrganizer.FindMods().First(m => m.Name == modName).modFile;
		var package = new RSDirectory(modName);
		var build = new BuildFile()
		{
			Language = GameCulture.FromCultureName(GameCulture.CultureName.English).Name,
			ModVersion = file.Version,
			ModName = file.Name,
		};
		package.AddFile(build);
		using var __ = file.Open();
		var ldstr = ExportLdstr(file);
		foreach (var translationFile in file.Where(entry => Path.GetExtension(entry.Name) == ".hjson"))
		{
			(var fileCulture, string prefix) = LocalizationLoader.GetCultureAndPrefixFromPath(translationFile.Name);
			var content = package.GetOrAddDirectory(fileCulture.Name);
			content.AddDirectory(ldstr with { });

			using var stream = file.GetStream(translationFile);
			using var reader = new StreamReader(stream, Encoding.UTF8, true);

			string translationFileContents = reader.ReadToEnd();

			try
			{
				RSDirectory dir;
				if (string.IsNullOrWhiteSpace(prefix))
				{
					dir = TextIO.ToRSDirectory(fileCulture.Name, modName, TextIO.EnumerateJson(HjsonValue.Parse(translationFileContents).Qo()));
				}
				else
				{
					Dictionary<string, string> translations = new();
					foreach (var (key, value) in TextIO.EnumerateJson(HjsonValue.Parse(translationFileContents).Qo()))
					{
						translations[prefix + '.' + KeyConverter.Normalize(key)] = value;
					}
					dir = TextIO.ToRSDirectory(fileCulture.Name, modName, translations);
				}
				dir.CopyTo(content);
			}
			catch (Exception ex)
			{
				ModHelper.Error("Exception", new CodeLocation("Hjson加载异常"), ex);
			}
		}
		return package;
	}
}